//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2013 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_ITERATOR_RANGE_H_
#define _BUOLA_ITERATOR_RANGE_H_

#include <buola/buola.h>
#include <buola/iterator/crange.h>
#include <buola/iterator/facade.h>
#include <set>
#include <deque>

namespace buola {

///\addtogroup iterators
///@{

///\name Ranges
//@{

///returns an iterator to the first element of the provided range

///The given range can be any iterable sequence. It works with anything that supports `std::begin`, plus
///pointers to characters, which are treated as strings.
template<typename tRange>
inline auto range_begin(const tRange &pRange) -> decltype(std::begin(pRange))
{
    return std::begin(pRange);
}

///\cond
template<typename tRange>
inline auto range_begin(tRange &pRange) -> decltype(std::begin(pRange))
{
    return std::begin(pRange);
}

inline const char *range_begin(const char *pRange)
{
    return pRange;
}

inline char *range_begin(char *pRange)
{
    return pRange;
}

inline const wchar_t *range_begin(const wchar_t *pRange)
{
    return pRange;
}

inline wchar_t *range_begin(wchar_t *pRange)
{
    return pRange;
}

template<typename tIt>
tIt range_begin(const std::pair<tIt,tIt> &pPair)
{
    return pPair.first;
}

template<typename tIt>
tIt range_end(const std::pair<tIt,tIt> &pPair)
{
    return pPair.second;
}

///\endcond

///returns an iterator past the last element of the provided range

///The given range can be any iterable sequence. It works with anything that supports `std::end`, plus
///pointers to characters, which are treated as strings.
///
///Arrays of characters are also treated as strings, so for example:
///~~~~~~~~~~~~~~~~~~~
///char lArray[10]="aa";
///msg_info() << range_end(lArray)-range_begin(lArray) << "\\n";
///~~~~~~~~~~~~~~~~~~~
///will output "2". If you want arrays to be treated as arrays instead of strings, use \ref array_range.
template<typename tRange>
inline auto range_end(const tRange &pRange) -> decltype(std::end(pRange))
{
    return std::end(pRange);
}

///\cond
template<typename tRange>
inline auto range_end(tRange &pRange) -> decltype(std::end(pRange))
{
    return std::end(pRange);
}

inline const char *range_end(const char *pRange)
{
    return pRange+std::strlen(pRange);
}

inline char *range_end(char *pRange)
{
    return pRange+std::strlen(pRange);
}

inline const wchar_t *range_end(const wchar_t *pRange)
{
    return pRange+std::wcslen(pRange);
}

inline wchar_t *range_end(wchar_t *pRange)
{
    return pRange+std::wcslen(pRange);
}

namespace detail {

    template<typename tRange>
    inline auto range_empty_helper(const tRange &pRange,int) -> decltype(pRange.empty())
    {
        return pRange.empty();
    }

    template<typename tRange,typename tDummy>
    inline bool range_empty_helper(const tRange &pRange,tDummy)
    {
        return range_begin(pRange)==range_end(pRange);
    }

/*namespace detail*/ }
///\endcond

///returns `true` if the given range is empty

///If \p pRange has an `empty()` function, it is used. Otherwise, the iterators to the beginning and end of the range
///are computed, and true is returned if they are equal.
template<typename tRange>
inline bool range_empty(const tRange &pRange)
{
    return detail::range_empty_helper(pRange,0);
}

///\cond
namespace detail {

    template<typename tRange>
    inline auto range_size_helper(const tRange &pRange,int) -> decltype(pRange.size())
    {
        return pRange.size();
    }

    template<typename tRange,typename tDummy>
    inline std::size_t range_size_helper(const tRange &pRange,tDummy)
    {
        return std::distance(range_begin(pRange),range_end(pRange));
    }

/*namespace detail*/ }
///\endcond

///returns the number of elements in the range

///If \p pRange has a `size()` function, it is used. Otherwise, the iterators to the beginning and end of the range
///are computed, and the distance between them is returned.
template<typename tRange>
inline std::size_t range_size(tRange &&pRange)
{
    return detail::range_size_helper(pRange,0);
}

///returns the range formed by iterators \p pFirst and \p pLast
template<typename tIt>
inline CRange<tIt> range(const tIt &pFirst,const tIt &pLast)
{
    return {pFirst,pLast};
}

///returns a `CRange<>` object formed by calling \ref range_begin and \ref range_end on \p pRange
template<typename tRange>
inline auto range(tRange &&pRange) -> CRange<decltype(range_begin(pRange))>
{
    return {range_begin(std::forward<tRange>(pRange)),range_end(std::forward<tRange>(pRange))};
}

///returns a `CRange<>` object formed by calling \ref range_begin and \ref range_end on \p pRange

///The difference with \ref range is that if \p pRange is an array of `char` it is not treated as a string.
template<typename tRange>
inline auto array_range(tRange &&pRange) -> CRange<decltype(std::begin(pRange))>
{
    return {std::begin(std::forward<tRange>(pRange)),std::end(std::forward<tRange>(pRange))};
}

///this traits operator returns the iterator type of the given range

///For containers, the iterator type could be different for const and non-const references, so use the
///const-qualified reference as the parameter.
template<typename tRange>
using ORangeIterator=ODecay<decltype(range_begin(std::declval<tRange>()))>;

///this traits operator returns the value type of the given range
template<typename tRange>
using ORangeValue=typename std::iterator_traits<ORangeIterator<tRange>>::value_type;

///\cond
namespace detail
{
    template<typename tValue>
    struct MRangeCloneTargetImpl
    {
        typedef std::vector<tValue> type;
    };

    template<>
    struct MRangeCloneTargetImpl<char>
    {
        typedef std::string type;
    };

    template<>
    struct MRangeCloneTargetImpl<wchar_t>
    {
        typedef std::wstring type;
    };

    template<typename tRange>
    inline auto fast_size_range_helper(const tRange &pRange,int) -> typename std::conditional<true,std::true_type,
                                                                              decltype(pRange.size())>::type
    {
        return std::true_type();
    }

    template<typename tRange,typename tDummy>
    buola::QIteratorSupports<ORangeIterator<tRange>,std::random_access_iterator_tag>
    inline fast_size_range_helper(const tRange &pRange,tDummy)
    {
        return {};
    }
}
///\endcond

///this traits query returns whether it's fast to query the size of a range

///it returns true for containers with a `size()` function and for random access iterator ranges
template<typename tRange>
using QFastSizeRange=decltype(detail::fast_size_range_helper(std::declval<tRange&>(),0));

///this traits typedef returns a suitable container for cloning \p tRange

///The container will have the same \c value_type as \p tRange, and it will in general be
///an \c std::vector, except if the \c value_type is a character type, in which case
///\c std::basic_string will be used.
///
///\tparam tRange The range that needs to be cloned
template<typename tRange>
using ORangeCloneSequence=typename detail::MRangeCloneTargetImpl<ORangeValue<tRange>>::type;

///returns a new container which holds a copy of the elements in \p pSrc

///ORangeCloneSequence is used to automatically select the type, based on the \c value_type of the range
template<typename tSrc>
inline ORangeCloneSequence<tSrc> range_clone(const tSrc &pSrc)
{
    return {range_begin(pSrc),range_end(pSrc)};
}

///returns a new container which holds a copy of the elements in \p pSrc

///\tparam tDst the type of sequence to create
template<typename tDst,typename tSrc>
inline tDst range_clone_to(const tSrc &pSrc)
{
    return {range_begin(pSrc),range_end(pSrc)};
}

template<typename tRange>
std::vector<typename tRange::value_type> make_vector(const tRange &pRange)
{
    return {range_begin(pRange),range_end(pRange)};
}

template<typename tRange>
std::list<typename tRange::value_type> make_list(const tRange &pRange)
{
    return {range_begin(pRange),range_end(pRange)};
}

template<typename tRange>
std::set<typename tRange::value_type> make_set(const tRange &pRange)
{
    return {range_begin(pRange),range_end(pRange)};
}

template<typename tRange>
std::deque<typename tRange::value_type> make_deque(const tRange &pRange)
{
    return {range_begin(pRange),range_end(pRange)};
}

template<typename tRange>
auto move_begin(tRange &&pRange) -> decltype(std::make_move_iterator(range_begin(pRange)))
{
    return std::make_move_iterator(range_begin(pRange));
}

template<typename tRange>
auto move_end(tRange &&pRange) -> decltype(std::make_move_iterator(range_end(pRange)))
{
    return std::make_move_iterator(range_end(pRange));
}

template<typename tRange>
auto move_range(tRange &&pRange) -> decltype(range(move_begin(pRange),move_end(pRange)))
{
    return range(move_begin(pRange),move_end(pRange));
}

//@}

///@}

/*namespace buola*/ }

#endif
